package com.lntea.factory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.lntea.beans.BeanDefinition;
import com.lntea.beans.BeanPostProcessor;

public abstract class AbstractBeanFactory implements BeanFactory {
	private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
	private List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();
	private List<String> beanDefinitionNames = new ArrayList<String>();
	
	public void registerBeanDefinition(String beanName,BeanDefinition beanDefinition){
		beanDefinitionMap.put(beanName, beanDefinition);
		beanDefinitionNames.add(beanName);
	}
	
	@Override
	public Object getBean(String beanName) throws Exception{
		BeanDefinition beanDefinition  = beanDefinitionMap.get(beanName);
		if(beanDefinition==null){
			throw new RuntimeException("bean for name '"+beanName+"' not register.");
		}
		Object bean = beanDefinition.getBean();
		if(bean==null){
			//bean = createBean(beanDefinition);
			bean = doCreateBean(beanDefinition);
			bean = initializeBean(bean,beanName);
			beanDefinition.setBean(bean);
		}
		return bean;
	}
	
	public void preInstantiateSingletons() throws Exception{
		for(String beanName : beanDefinitionNames){
			getBean(beanName);
		}
	}

	protected Object initializeBean(Object bean,String name) throws Exception {
		for(BeanPostProcessor beanPostProcessor : beanPostProcessors){
			beanPostProcessor.postProcessBeforeInitialization(bean, name);
		}
		
		//invoke init-method
		
		for(BeanPostProcessor beanPostProcessor : beanPostProcessors){
			beanPostProcessor.postProcessAfterInitialization(bean, name);
		}
		return bean;
	}
	
	protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception{
		Object bean = createInstance(beanDefinition);
		beanDefinition.setBean(bean);
		applyPropertyValues(bean,beanDefinition);
		return bean;
	}

	protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception{
	}

	protected Object createInstance(BeanDefinition beanDefinition) throws Exception {
		return beanDefinition.getBeanClass().newInstance();
	}
	
	public <T> List<T> getBeansForType(Class<T> type) throws Exception{
		List<T> list = new ArrayList<T>();
		for(String beanName : beanDefinitionNames){
			if(type.isAssignableFrom(beanDefinitionMap.get(beanName).getBeanClass())){
				list.add((T)getBean(beanName));
			}
		}
		return list;
	}
	
	public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor){
		beanPostProcessors.add(beanPostProcessor);
	}

//	private Object createBean(BeanDefinition beanDefinition) {
//		String beanClassName = beanDefinition.getBeanClassName();
//		if(beanClassName==null){
//			throw new RuntimeException("bean class name cannot be null.");
//		}
//		try {
//			//create newInstance
//			Class clazz = Class.forName(beanClassName);
//			Constructor constructor = clazz.getDeclaredConstructor(null);
//			Object bean = constructor.newInstance(null);
//			//inject property
//			PropertyValues propertyValues = beanDefinition.getPropertyValues();
//			for(PropertyValue propertyValue : propertyValues.getPropertyValues()){
//				String name = propertyValue.getName();
//				Object value = propertyValue.getValue();
//				if(value instanceof BeanReference){
//					value = getBean(((BeanReference)value).getRef());
//				}
//				Method method = clazz.getDeclaredMethod("set"+name.substring(0, 1).toUpperCase()+name.substring(1),
//						value.getClass());
//				method.invoke(bean, value);
//			}
//			return bean;
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//		
//		return null;
//	}
}
